package main

import (
	"database/sql"
	"fmt"
	"github.com/antchfx/htmlquery"
	_ "github.com/go-sql-driver/mysql"
	"io"
	"log"
	"net/http"
	"strings"
)

const (
	DBUser     = "root"
	DBPassword = ""
	DBName     = "douban"
)

func main() {
	// 打开数据库连接
	db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@/%s", DBUser, DBPassword, ""))
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// 开始事务
	tx, err := db.Begin()
	if err != nil {
		log.Fatal(err)
	}

	// 延迟函数，用于检查是否有错误并回滚事务
	defer func() {
		if err := recover(); err != nil {
			log.Println("Rolling back transaction due to:", err)
			tx.Rollback()
		}
	}()

	// 创建数据库如果不存在
	err = createDatabaseIfNotExists(tx, DBName)
	if err != nil {
		log.Fatal(err)
	}

	// 提交事务，确保数据库 'douban' 已经创建
	err = tx.Commit()
	if err != nil {
		log.Fatal(err)
	}

	// 开始新事务
	tx, err = db.Begin()
	if err != nil {
		log.Fatal(err)
	}

	// 切换到 'douban' 数据库
	_, err = tx.Exec("USE " + DBName)
	if err != nil {
		log.Fatal(err)
	}

	// 如果不存在，则创建表格
	createTables(tx)

	// 获取数据并插入到数据库
	titles, err := fetchTitle()
	if err != nil {
		log.Fatal(err)
	}
	pictures, err := fetchPicture()
	if err != nil {
		log.Fatal(err)
	}
	scores, err := fetchScore()
	if err != nil {
		log.Fatal(err)
	}
	directors, err := fetchDirector()
	if err != nil {
		log.Fatal(err)
	}
	evaluates, err := fetchEvaluate()
	if err != nil {
		log.Fatal(err)
	}

	// 插入数据到数据库
	insertData(tx, titles, pictures, scores, directors, evaluates)

	// 提交事务
	err = tx.Commit()
	if err != nil {
		log.Fatal(err)
	}
}

type sqlCommon interface {
	Exec(query string, args ...interface{}) (sql.Result, error)
	QueryRow(query string, args ...interface{}) *sql.Row
	Prepare(query string) (*sql.Stmt, error)
}

// 辅助函数用于创建数据库（如果不存在）
func createDatabaseIfNotExists(db sqlCommon, dbName string) error {
	// 检查数据库是否存在
	var result string
	err := db.QueryRow("SHOW DATABASES LIKE '" + dbName + "'").Scan(&result)
	if err != nil && err != sql.ErrNoRows {
		return err
	}

	// 如果数据库不存在，则创建
	if result != dbName {
		_, err := db.Exec("CREATE DATABASE " + dbName)
		if err != nil {
			return err
		}
		fmt.Printf("数据库 '%s' 创建成功\n", dbName)
	}

	return nil
}

func createTables(db sqlCommon) {
	// 如果不存在，则创建 'movies' 表格6
	_, err := db.Exec(`
		CREATE TABLE IF NOT EXISTS movies (
			id INT AUTO_INCREMENT PRIMARY KEY,
			title VARCHAR(255),
			picture_url VARCHAR(255),
			score VARCHAR(255),
			director VARCHAR(255),
			evaluate VARCHAR(255)
		)
	`)
	if err != nil {
		log.Fatal(err)
	}
}

func insertData(db sqlCommon, titles, pictures, scores, directors, evaluates []string) {
	// 准备插入语句
	stmt, err := db.Prepare("INSERT INTO movies (title, picture_url, score, director, evaluate) VALUES (?, ?, ?, ?, ?)")
	if err != nil {
		log.Fatal(err)
	}
	defer stmt.Close()

	// 插入数据到 'movies' 表格
	for i := 0; i < len(titles); i++ {
		_, err := stmt.Exec(titles[i], pictures[i], scores[i], directors[i], evaluates[i])
		if err != nil {
			log.Fatal(err)
		}
	}
	fmt.Println("数据插入成功！")
}

func fetchTitle() ([]string, error) {
	var client = http.Client{}
	var result []string
	baseURL := "https://movie.douban.com/top250?start=%d&filter="

	// 循环请求
	for start := 0; start < 250; start += 25 {
		url := fmt.Sprintf(baseURL, start)

		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			return nil, err
		}
		req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")

		resp, err := client.Do(req)
		if err != nil {
			return nil, err
		}
		defer func(Body io.ReadCloser) {
			err := Body.Close()
			if err != nil {
				panic(err)
			}
		}(resp.Body)

		doc, err := htmlquery.Parse(resp.Body)
		if err != nil {
			return nil, err
		}

		ols := htmlquery.Find(doc, "//*[@id=\"content\"]/div/div[1]/ol/li")

		// 解析每一个电影信息
		for _, ol := range ols {
			name1 := htmlquery.FindOne(ol, "//div/div[2]/div[1]/a/span[1]")
			name2 := htmlquery.FindOne(ol, "//div/div[2]/div[1]/a/span[2]")
			name3 := htmlquery.FindOne(ol, "//div/div[2]/div[1]/a/span[3]")

			var text1, text2, text3 string

			if name1 != nil {
				text1 = htmlquery.InnerText(name1)
			}

			if name2 != nil {
				text2 = htmlquery.InnerText(name2)
			}

			if name3 != nil {
				text3 = htmlquery.InnerText(name3)
			}

			name4 := text1 + text2 + text3
			result = append(result, name4)
		}

	}

	return result, nil
}

func fetchPicture() ([]string, error) {
	var client = http.Client{}
	var result []string
	baseURL := "https://movie.douban.com/top250?start=%d&filter="

	// 循环请求
	for start := 0; start < 250; start += 25 {
		url := fmt.Sprintf(baseURL, start)

		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			return nil, err
		}
		req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")

		resp, err := client.Do(req)
		if err != nil {
			return nil, err
		}
		defer func(Body io.ReadCloser) {
			err := Body.Close()
			if err != nil {
				panic(err)
			}
		}(resp.Body)

		doc, err := htmlquery.Parse(resp.Body)
		if err != nil {
			return nil, err
		}
		ols := htmlquery.Find(doc, "//div/div[1]/ol/li")
		for _, ol := range ols {
			name := htmlquery.FindOne(ol, "//div/div[1]/a/img/@src")
			text := htmlquery.InnerText(name)

			result = append(result, text)
		}

	}

	return result, nil
}

func fetchScore() ([]string, error) {
	var client = http.Client{}
	var result []string
	baseURL := "https://movie.douban.com/top250?start=%d&filter="

	// 循环请求
	for start := 0; start < 250; start += 25 {
		url := fmt.Sprintf(baseURL, start)

		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			return nil, err
		}
		req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")

		resp, err := client.Do(req)
		if err != nil {
			return nil, err
		}
		defer func(Body io.ReadCloser) {
			err := Body.Close()
			if err != nil {
				panic(err)
			}
		}(resp.Body)

		doc, err := htmlquery.Parse(resp.Body)
		if err != nil {
			return nil, err
		}

		ols := htmlquery.Find(doc, "//*[@id=\"content\"]/div/div[1]/ol/li")

		// 解析每一个电影信息
		for _, ol := range ols {
			name1 := htmlquery.FindOne(ol, "//div/div[2]/div[2]/div/span[1]/@class")
			name2 := htmlquery.FindOne(ol, "//div/div[2]/div[2]/div/span[2]")
			name3 := htmlquery.FindOne(ol, "//div/div[2]/div[2]/div/span[4]")

			var text1, text2, text3 string

			if name1 != nil {
				text1 = htmlquery.InnerText(name1)
			}

			if name2 != nil {
				text2 = htmlquery.InnerText(name2)
			}

			if name3 != nil {
				text3 = htmlquery.InnerText(name3)
			}

			name4 := text1 + " " + text2 + " " + text3
			result = append(result, name4)
		}

	}

	return result, nil
}

func fetchDirector() ([]string, error) {
	var client = http.Client{}
	var result []string
	baseURL := "https://movie.douban.com/top250?start=%d&filter="

	// 循环请求
	for start := 0; start < 250; start += 25 {
		url := fmt.Sprintf(baseURL, start)

		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			return nil, err
		}
		req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")

		resp, err := client.Do(req)
		if err != nil {
			return nil, err
		}
		defer func(Body io.ReadCloser) {
			err := Body.Close()
			if err != nil {
				panic(err)
			}
		}(resp.Body)

		doc, err := htmlquery.Parse(resp.Body)
		if err != nil {
			return nil, err
		}
		ols := htmlquery.Find(doc, "//div/div[1]/ol/li")
		for _, ol := range ols {
			name := htmlquery.FindOne(ol, "//div/div[2]/div[2]/p[1]")
			text := htmlquery.InnerText(name)
			text1 := strings.TrimSpace(text)

			result = append(result, text1)
		}

	}

	return result, nil
}

func fetchEvaluate() ([]string, error) {
	var client = http.Client{}
	var result []string
	baseURL := "https://movie.douban.com/top250?start=%d&filter="

	// 循环请求
	for start := 0; start < 250; start += 25 {
		url := fmt.Sprintf(baseURL, start)

		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			return nil, err
		}
		req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")

		resp, err := client.Do(req)
		if err != nil {
			return nil, err
		}
		defer func(Body io.ReadCloser) {
			err := Body.Close()
			if err != nil {
				panic(err)
			}
		}(resp.Body)

		doc, err := htmlquery.Parse(resp.Body)
		if err != nil {
			return nil, err
		}
		ols := htmlquery.Find(doc, "//div/div[1]/ol/li")
		for _, ol := range ols {
			name := htmlquery.FindOne(ol, "//div/div[2]/div[2]/p[2]/span")

			var text string
			if name != nil {
				text = htmlquery.InnerText(name)
			} else if name == nil {
				text = "  "
			}

			result = append(result, text)
		}

	}

	return result, nil
}
